<?php

namespace services\base;

use api\modules\v1\forms\base\DepartmentForm;
use common\components\Service;
use common\helpers\CommonHelper;
use common\helpers\RedisHelper;
use common\models\base\Department;
use common\models\member\Member;
use common\models\member\MemberDepartment;
use services\api\AccessTokenService;
use services\log\LogMemberOpService;
use services\member\AttrService;
use services\member\MemberDepartmentService;
use services\member\MemberService;
use services\rbac\RoleService;
use services\rbac\UserRoleService;
use Yii;

/**
 * Class DepartmentService
 * @package services\base
 */
class DepartmentService extends Service
{
    /**
     * 部门列表
     * @param array $params
     * @param int $page
     * @param int $pageSize
     * @return array
     */
    public function getListPage(array $params = [], int $page = 0, int $pageSize = 0): array
    {
        $query = Department::find()->alias('d')
            ->select([
                'd.*',
                'count(DISTINCT md.department_id) member_num'
            ])
            ->leftJoin(MemberDepartment::tableName().' md', 'md.department_id = d.id')
            ->leftJoin(Member::tableName().' m', "m.id = md.member_id AND m.status IN (".Member::STATUS_INVALID.", ".Member::STATUS_ENABLE.")")
            ->groupBy('d.id');
        if (isset($params['status']) && is_numeric($params['status'])) {
            $query->andWhere(['d.status' => $params['status']]);
        } else {
            $query->andWhere(['d.status' => [Department::STATUS_INVALID, Department::STATUS_ENABLE]]);
        }
        // 排序
        $order = [];
        if (isset($params['order_column']) && $params['order_column']) {
            $sort = isset($params['order_sort']) && $params['order_sort'] == 'asc' ? SORT_ASC : SORT_DESC;
            switch ($params['order_column']) {
                case 'member_num':
                    $order[$params['order_column']] = $sort;
                    break;
                default:
                    break;
            }
            $order && $order['d.id'] = SORT_ASC;
        }
        empty($order) && $order = ['d.status' => SORT_DESC, 'd.sort' => SORT_ASC, 'd.id' => SORT_ASC];

        return $this->getListPageRes($query, $page, $pageSize, $order);
    }

    /**
     * 格式化列表
     * @param array $data
     * @return array
     */
    public function getListPageFormat(array $data = []): array
    {
        $list = [];
        foreach ($data as $row) {
            $list[] = [
                'id' => $row['id'],
                'department_name' => $row['department_name'],
                'member_num' => $row['member_num'],
                'sort' => $row['sort'],
//                'status' => $row['status'],
                'status_str' => Department::$statuss[$row['status']] ?? '',
//                'created_at_str' => DateHelper::timestampToDate($row['created_at']),
            ];
        }
        return $list;
    }


    /**
     * 部门树状列表【改版】
     * @return array
     */
    public function getTreeList(): array
    {
        $query = Department::find()
            ->select(['*'])
            ->andWhere(['status' => Department::STATUS_ENABLE]);
        // 排序
        $order = ['level' => SORT_ASC, 'sort' => SORT_ASC, 'id' => SORT_ASC];
        // 结果
        return $query->orderBy($order)->asArray()->all();
    }

    /**
     * 格式化列表
     * @param array $data
     * @param int $pid
     * @return array
     */
    public function getTreeListFormat(array $data, int $pid = 0): array
    {
        $list = [];
        foreach ($data as $row) {
            if ($row['pid'] != $pid) {
                continue;
            }

            // 子级
            $child = $this->getTreeListFormat($data, $row['id']);

            $list[] = [
                'id' => $row['id'],
                'level' => $row['level'],
                'department_name' => $row['department_name'],
                'children' => $child,
//                'created_at_str' => DateHelper::timestampToDate($row['created_at']),
            ];
        }
        return $list;
    }

    /**
     * @param int $id
     * @return array
     */
    public static function getParent(int $id): array
    {
        $data = Department::findOne($id);
        $allId = explode('-', $data->tree_id);
        return Department::find()->select(['department_name'])->where(['id' => $allId])->asArray()->column();
    }

    /**
     * 列表筛选项
     * @return array
     */
    public function getSelectCondition(): array
    {
        return [
            'status' => self::getStatusCondition(),
        ];
    }

    /**
     * 状态下拉筛选项
     * @return array[]
     */
    public static function getStatusCondition(): array
    {
        return [
            ['value' => Department::STATUS_ENABLE, 'label' => Department::$statuss[Department::STATUS_ENABLE]],
            ['value' => Department::STATUS_INVALID, 'label' => Department::$statuss[Department::STATUS_INVALID]],
        ];
    }

    /**
     * 数据权限下拉筛选项
     * @return array[]
     */
    public static function getDataLevelCondition(): array
    {
        $dataLevels = Department::$dataLevels;
        unset($dataLevels[Department::DATA_LEVEL_ALL]);
        $data = [];
        foreach ($dataLevels as $key => $row) {
            $data[] = [
                'value' => $key,
                'label' => $row,
            ];
        }
        return $data;
    }

    /**
     * 根据角色Ids获取有效角色列表
     * @param array|null $ids
     * @param null $orIds
     * @return Department[]
     */
    public static function getEnableListByIds(?array $ids = null, $orIds = null): array
    {
        $query = Department::find()
            ->where([
                'status' => Department::STATUS_ENABLE
            ]);
        if (!is_null($ids)) {
            $query->andWhere(['id' => $ids]);
        }
        if ($orIds) {
            $query->orWhere([
                'id' => $orIds,
                'status' => [Department::STATUS_ENABLE, Department::STATUS_INVALID]
            ]);
        }
        return $query->orderBy(['sort' => SORT_ASC, 'id' => SORT_ASC])->all();
    }

    /**
     * 根据Ids获取部门列表【禁用 + 正常】
     * @param array $ids
     * @return array
     */
    public static function getNormalListByIds(array $ids = []): array
    {
        $query = Department::find()
            ->select(['*'])
            ->where([
                'status' => [Department::STATUS_INVALID, Department::STATUS_ENABLE],
            ]);
        if ($ids) {
            $query->andWhere(['id' => $ids]);
        }
        return $query->orderBy(['sort' => SORT_ASC, 'id' => SORT_ASC])->all();
    }

    /**
     * 根据Ids获取部门列表【禁用 + 正常】
     * @param array $departmentIds
     * @return Department[]
     */
    public static function getNormalCacheListByIds(array $departmentIds): array
    {
        $data = [];
        foreach ($departmentIds as $departmentId) {
            $department = RedisHelper::getDepartment($departmentId);
            if (empty($department) || $department->status == Department::STATUS_DELETE) {
                continue;
            }
            $data[] = $department;
        }
        return $data;
    }

    /**
     * 获取下拉列表筛选项
     * @return array
     */
    public static function getListCondition(): array
    {
        return CommonHelper::getListCondition(self::getNormalListByIds(), ['value_column' => 'id', 'label_column' => 'department_name']);
    }

    /**
     * 详情绑定项目列表
     * @param int|array|null $orId
     * @return array
     */
    public static function getEditCondition($orId = null): array
    {
        return CommonHelper::getListCondition(self::getEnableListByIds(null, $orId), ['value_column' => 'id', 'label_column' => 'department_name']);
    }

    /**
     * 部门详情
     * @param int $id
     * @param string $scenario
     * @return array
     */
    public function getInfo(int $id, string $scenario = 'create'): array
    {
        $data = [];
        // 部门信息
        $department = Department::findOne($id);
        if ($department) {
            // 部门人员IDs
            $departmentMemberIds = MemberDepartmentService::getMemberIdsByDepartmentId($department->id);
            $data = [
                'id' => $department->id,
                'pid' => $department->pid,
                'department_name' => $department->department_name,
                'data_level' => $department->data_level,
                'select_role_ids' => $department->select_role_ids ?: [],
                'default_role_id' => $department->default_role_id,
                'leader_member_ids' => array_intersect($department->leader_member_ids ?: [], $departmentMemberIds),
            ];
        }
        // 筛选条件
        $condition = [
            'data_level' => self::getDataLevelCondition(),
            'role' => self::getSelectRoleCondition($scenario == 'create' ? $department : Department::findOne($department->pid)),
            'leader_member' => $scenario == 'edit' && $department ? MemberService::getEditCondition($departmentMemberIds, $data['leader_member_ids']) : [],// 部门负责人【新增不存在、编辑选择】
        ];
        // 清洗数据
        if ($scenario == 'edit' && $department) {
            $data['select_role_ids'] = array_intersect($data['select_role_ids'], array_column($condition['role'], 'value'));
            if (in_array($data['default_role_id'], $data['select_role_ids']) === false) {
                $data['default_role_id'] = 0;
            }
            $data['leader_member_ids'] = array_intersect($data['leader_member_ids'], array_column($condition['leader_member'], 'value'));
        }
        return array_merge($scenario == 'create' ? [] : $data, ['condition' => $condition]);
    }

    /**
     * 是否存在子部门
     * @param int $id
     * @return bool
     */
    public static function hasChildDepartment(int $id): bool
    {
        return (bool)Department::findOne(['pid' => $id, 'status' => Department::STATUS_ENABLE]);
    }

    /**
     * 部门删除详情
     * @param int $id
     * @return array
     */
    public function getDeleteInfo(int $id): array
    {
        // 部门信息
        $department = Department::findOne($id);
        if (empty($department)) {
            return CommonHelper::api(422, '部门不存在');
        }

        // 部门人数
        $departmentMemberIds = MemberDepartmentService::getMemberIdsByDepartmentId($department->id);
        $memberList = MemberService::getNormalListByIds($departmentMemberIds);

        return [
            'has_child' => self::hasChildDepartment($department->id) ? 1 : 0,
            'member_cnt' => count($memberList),
        ];
    }

    /**
     * 获取筛选角色列表
     * @param Department|null $department
     * @param string $scenario
     * @return array
     */
    public static function getSelectRoleCondition(?Department $department): array
    {
        return RoleService::getEditCondition($department ? ($department->select_role_ids ?: []) : null, $department->select_role_ids ?? null);
    }

    /**
     * 获取部门当前层级最大排序值
     * @param int $pid
     * @return int
     */
    public static function getParentDepartmentMaxSort(int $pid): int
    {
        $sort = Department::find()->where(['pid' => $pid, 'status' => Department::STATUS_ENABLE])->max('sort');
        return is_numeric($sort) ? $sort + 1 : 100;
    }

    /**
     * 获取当前部门最大子级层级差值
     * @param int $id
     * @return int
     */
    public static function getDepartmentDiffLevelNum(int $id): int
    {
        $department = Department::findOne($id);
        $maxLevel = Department::find()
            ->where(['like', 'tree_id', "{$department->tree_id}%", false])
            ->andWhere(['status' => Department::STATUS_ENABLE])
            ->max('level');
        return $maxLevel - $department->level;
    }

    /**
     * 编辑部门
     * @param Department $department
     * @param DepartmentForm $model
     * @return Department
     * @throws \Exception
     */
    public static function editModel(Department $department, DepartmentForm $model): Department
    {
        if (in_array($model->getScenario(), ['create', 'move'])) {
            $pDepartment = $model->getParentDepartment();
            $department->pid = $model->pid ?: 0;
            $department->level = ($pDepartment->level ?? 0) + 1;
            $department->status = Department::STATUS_ENABLE;
            $department->sort = $model->getScenario() == 'create' || empty($afterDepartment = $model->getAfterDepartment()) ? self::getParentDepartmentMaxSort($department->pid) : $afterDepartment->sort;
        }
        if (in_array($model->getScenario(), ['create', 'edit'])) {
            $department->department_name = $model->department_name;
            $department->data_level = $model->data_level;
            $department->select_role_ids = $model->getSelectRoleIds();
            $department->default_role_id = $model->default_role_id;
            $department->leader_member_ids = $model->getLeaderMemberIds();
        }
        $department->saveAndCheckResult();
        // 树级ID，创建后主键ID
        if (in_array($model->getScenario(), ['create', 'move'])) {
            $pDepartment = $model->getParentDepartment();
            $department->tree_id = ($pDepartment->tree_id ?? '')."{$department->id}-";
            if ($model->getScenario() == 'move' && $pDepartment) {
                $department->select_role_ids = array_values(array_intersect($pDepartment->select_role_ids, $department->select_role_ids));
                $department->default_role_id = in_array($department->default_role_id, $department->select_role_ids) ? $department->default_role_id : 0;
            }
            $department->saveAndCheckResult();
        }
        return $department;
    }

    /**
     * 获取子部门列表
     * @param string $treeId
     * @return Department[]
     */
    public static function getChildDepartmentList(string $treeId): array
    {
        /**
         * @var Department[] $departmentList
         */
        return Department::find()
            ->where(['like', 'tree_id', "{$treeId}%", false])
            ->andWhere(['status' => Department::STATUS_ENABLE])
            ->andWhere(['!=', 'tree_id', $treeId])
            ->all();
    }

    /**
     * 更新子部门【层级、tree_id】
     * @param Department $old
     * @param Department $new
     * @return void
     * @throws \Exception
     */
    public static function editChildLevelTree(Department $old, Department $new): void
    {
        if ($old->pid == $new->pid) {
            return;
        }
        $departmentList = self::getChildDepartmentList($old->tree_id);
        $diffLevel = $old->level - $new->level;
        foreach ($departmentList as $department) {
            $department->level = $department->level - $diffLevel;
            $department->tree_id = str_replace($old->tree_id, $new->tree_id, $department->tree_id);
            $department->saveAndCheckResult();

            RedisHelper::setDepartment($department);
        }
    }

    /**
     * 更新子部门角色、默认角色
     * @param Department $old
     * @param Department $new
     * @return void
     * @throws \Exception
     */
    public static function editChildRoleIds(Department $old, Department $new): void
    {
        $diffRoleIds = array_diff($old->select_role_ids, $new->select_role_ids);
        if (empty($diffRoleIds)) {
            return;
        }

        // 子部门IDs
        $departmentList = self::getChildDepartmentList($old->tree_id);
        foreach ($departmentList as $department) {
            $department->select_role_ids = array_values(array_diff($department->select_role_ids, $diffRoleIds));
            if (in_array($department->default_role_id, $diffRoleIds)) {
                $department->default_role_id = 0;
            }
            $department->saveAndCheckResult();

            RedisHelper::setDepartment($department);
        }

        // 部门默认角色清空【部门下人员退出登陆】
        UserRoleService::deleteDepartmentRole(array_column($departmentList, 'id'), $diffRoleIds);
    }

    /**
     * 更新转移前后下级部门【层级、tree_id】
     * @param Department $old
     * @param Department $new
     * @return void
     * @throws \Exception
     */
    public static function editAfterModel(Department $old, Department $new): void
    {
        // 转移前，后面部门+1
        $oldAfterDepartment = Department::find()
            ->where([
                'pid' => $old->pid,
                'status' => Department::STATUS_ENABLE
            ])
            ->andWhere(['>', 'sort', $old->sort])
            ->andWhere(['!=', 'id', $new->id])
            ->all();
        $oldIds = array_column($oldAfterDepartment, 'id');
        Department::updateAllCounters(['sort' => -1], ['id' => $oldIds]);

        // 转移后，后面部门+1
        $newAfterDepartment = Department::find()
            ->where([
                'pid' => $new->pid,
                'status' => Department::STATUS_ENABLE
            ])
            ->andWhere(['>=', 'sort', $new->sort])
            ->andWhere(['!=', 'id', $new->id])
            ->all();
        $newIds = array_column($newAfterDepartment, 'id');
        Department::updateAllCounters(['sort' => 1], ['id' => $newIds]);

        // 更细缓存
        RedisHelper::delDepartment(array_merge($oldIds, $newIds));
    }

    /**
     * 新增部门
     * @param DepartmentForm $model
     * @return array
     */
    public function create(DepartmentForm $model): array
    {
        try {
            $transaction = Yii::$app->db->beginTransaction();

            // 1.新增部门
            $department = new Department();
            $department = self::editModel($department, $model);

            // 2.日志
            if (LogMemberOpService::createDepartmentLog($model, $department)) {
                $department->operation_id = Yii::$app->user->identity->member_id;
                $department->operation_name = Yii::$app->user->identity->member->realname;
                $department->saveAndCheckResult();
            }

            $transaction->commit();
            return ['id' => $department->id];
        } catch (\Exception $e) {
            $transaction->rollBack();
            throw $e;
        }
    }

    /**
     * 编辑部门
     * @param DepartmentForm $model
     * @return array
     */
    public function edit(DepartmentForm $model): array
    {
        try {
            $transaction = Yii::$app->db->beginTransaction();

            // 1.更新部门
            $oldDepartment = new Department();
            $oldDepartment->setAttributes($model->getDepartment()->getAttributes());
            $department = self::editModel($model->getDepartment(), $model);
            // 1-2.修改子部门选中角色IDs
            self::editChildRoleIds($oldDepartment, $department);
            // 1-3.删除部门人员绑定默认角色【部门设置默认角色，人员绑定对应角色解除绑定】
            UserRoleService::deleteDepartmentRole($department->id, $department->default_role_id);
            // 1-4.默认角色变更，强制整部门下线
            if ($department->default_role_id != $oldDepartment->default_role_id) {
                AccessTokenService::clearMemeberToken(MemberDepartmentService::getMemberIdsByDepartmentId($department->id));
            }

            // 2.日志
            if (LogMemberOpService::createDepartmentLog($model, $department, $oldDepartment)) {
                $department->operation_id = Yii::$app->user->identity->member_id;
                $department->operation_name = Yii::$app->user->identity->member->realname;
                $department->saveAndCheckResult();
            }

            // 3.更新缓存
            RedisHelper::setDepartment($department);

            $transaction->commit();
            return ['id' => $department->id];
        } catch (\Exception $e) {
            $transaction->rollBack();
            throw $e;
        }
    }

    /**
     * 移动部门【变更父级】
     * @param DepartmentForm $model
     * @return array
     */
    public function move(DepartmentForm $model): array
    {
        try {
            $transaction = Yii::$app->db->beginTransaction();

            // 1.更新部门
            $oldDepartment = new Department();
            $oldDepartment->setAttributes($model->getDepartment()->getAttributes());
            $department = self::editModel($model->getDepartment(), $model);
            // 1-2.更新子部门层级
            self::editChildLevelTree($oldDepartment, $department);
            // 1-3.修改子部门选中角色IDs
            self::editChildRoleIds($oldDepartment, $department);

            // 2.日志
            if (LogMemberOpService::createDepartmentLog($model, $department, $oldDepartment)) {
                // 2-2.更新转移前后部门
                self::editAfterModel($oldDepartment, $department);
                // 2-3.更新操作人
                $department->operation_id = Yii::$app->user->identity->member_id;
                $department->operation_name = Yii::$app->user->identity->member->realname;
                $department->saveAndCheckResult();
            }

            // 3.更新缓存
            RedisHelper::setDepartment($department);

            $transaction->commit();
            return ['id' => $department->id];
        } catch (\Exception $e) {
            $transaction->rollBack();
            throw $e;
        }
    }

    /**
     * 删除部门
     * @param DepartmentForm $model
     * @return array
     */
    public function delete(DepartmentForm $model): array
    {
        try {
            $transaction = Yii::$app->db->beginTransaction();

            // 1.更新部门
            $department = $model->getDepartment();
            $department->status = Department::STATUS_DELETE;
            $department->operation_id = Yii::$app->user->identity->member_id;
            $department->operation_name = Yii::$app->user->identity->member->realname;
            $department->saveAndCheckResult();

            // 1-2.删除子部门
            $childDepartmentList = DepartmentService::getChildDepartmentList($department->tree_id);
            $childIds = array_column($childDepartmentList, 'id');
            Department::updateAll([
                'status' => Department::STATUS_DELETE,
                'operation_id' => Yii::$app->user->identity->member_id,
                'operation_name' => Yii::$app->user->identity->member->realname,
            ], ['id' => $childIds]);

            // 2.日志
            LogMemberOpService::createDepartmentLog($model, $department);

            // 3.解绑人员部门
            $ids = array_merge([$department->id], $childIds);
            AttrService::unBindDepartment($ids, $department->select_role_ids);

            // 4.更新缓存
            RedisHelper::delDepartment($ids);

            $transaction->commit();
            return ['id' => $department->id];
        } catch (\Exception $e) {
            $transaction->rollBack();
            throw $e;
        }
    }
}
